/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.web.impl.thread;

import static net.paoding.rose.impl.util.PlaceHolderUtils.*;
import static net.paoding.rose.impl.util.PlaceHolderUtils.PLACEHOLDER_SUFFIX;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * @author qieqie.wang@paoding.net
 * 
 */
public class ActionPathMapping implements Comparable<ActionPathMapping> {

	private static Log logger = LogFactory.getLog(ActionPathMapping.class);

	private String mapping;
	private Pattern paramsPattern;
	private ActionInvocation invocation;
	private static final String[] EMPTY = new String[0];
	private String[] paramNames = EMPTY;
	private List<String> constaints = new ArrayList<String>(2);

	public ActionPathMapping(String mapping, ActionInvocation invocation) {
		this.mapping = mapping;
		this.invocation = invocation;
		compileMapping();
	}

	private void compileMapping() {
		if (mapping.length() > 0 && mapping.charAt(0) != '/') {
			mapping = '/' + mapping;
		}
		if (mapping.equals("/")) {
			mapping = "";
		}
		while (mapping.length() > 1 && mapping.endsWith("/")) {
			mapping = mapping.substring(0, mapping.length() - 1);
		}
		ArrayList<String> paramNames = new ArrayList<String>();
		int nextIndex = 0;
		String defaultRegex = "([[^/].]+)";
		int startIndex = mapping.indexOf(PLACEHOLDER_PREFIX);
		if (startIndex == -1) {
			this.paramsPattern = Pattern.compile("^" + mapping + "/?$");
		} else {
			int paramIndex = 1; // 记录参数的位置，从0开始，用于匿名参数转化为param1的形式
			StringBuilder buf = new StringBuilder(mapping);
			while (startIndex != -1) {
				int endIndex = -1;
				int cc = 0;
				for (int i = startIndex + PLACEHOLDER_PREFIX.length(); i < buf
						.length(); i++) {
					char ch = buf.charAt(i);
					if (ch == PLACEHOLDER_INNER_PREFIX) {
						cc++;
					} else if (ch == PLACEHOLDER_SUFFIX_CHAR) {
						cc--;
					}
					if (cc < 0) {
						endIndex = i;
						break;
					}
				}
				if (endIndex != -1) {
					String paramName = null;
					String regex = defaultRegex;
					for (int i = startIndex + PLACEHOLDER_PREFIX.length(); i < endIndex; i++) {
						if (buf.charAt(i) == ':') {
							paramName = buf.substring(startIndex
									+ PLACEHOLDER_PREFIX.length(), i);
							regex = buf.substring(i + 1, endIndex);
							if (regex.length() == 0) {
								regex = defaultRegex;
							} else if (regex.equals('*')) {
								regex = "(.*)";
							} else if (regex.equals('+')) {
								regex = "(.+)";
							} else if (regex.equals('?')) {
								regex = "(.?)";
							} else if (regex.charAt(0) != '(') {
								regex = '(' + regex + ')';
							}
							break;
						}
					}
					if (paramName == null) {
						paramName = buf.substring(startIndex
								+ PLACEHOLDER_PREFIX.length(), endIndex);
					}
					if (paramName.length() == 0) {
						paramName = "param" + paramIndex;
					}
					constaints.add(buf.substring(nextIndex, startIndex));
					paramNames.add(paramName);
					buf.replace(startIndex, endIndex
							+ PLACEHOLDER_SUFFIX.length(), regex);
					nextIndex = startIndex + regex.length();
					startIndex = buf.indexOf(PLACEHOLDER_PREFIX, nextIndex);
					paramIndex++;
				} else {
					startIndex = -1;
				}
			}
			// 最后的constaint
			if (nextIndex < buf.length()) {
				constaints.add(buf.substring(nextIndex));
			}
			this.paramNames = paramNames.toArray(new String[paramNames.size()]);
			this.paramsPattern = Pattern.compile("^" + buf.toString() + "/?$");
		}
		if (logger.isDebugEnabled()) {
			logger.debug("map '" + mapping + "' to pattern '"
					+ paramsPattern.toString() + "'");
			logger.debug("'" + mapping + "'.params="
					+ Arrays.toString(this.paramNames));
			logger.debug("'" + mapping + "'.constaints=" + this.constaints);
		}
		//
	}

	public String getMapping() {
		return mapping;
	}

	public ActionInvocation getInvocation() {
		return invocation;
	}

	public boolean isPlaceHoldered() {
		return this.paramNames.length > 0;
	}

	private static final MatchResult EMPTY_PARAM_MATCH_RESULT = new MatchResult();

	public MatchResult match(HttpServletRequest request, String actionPath) {
		if (!invocation.isRequestMethodSupported(request.getMethod())) {
			return null;
		}
		Matcher matcher = paramsPattern.matcher(actionPath);
		if (!matcher.find()) {
			return null;
		}
		if (paramNames.length == 0) {
			return EMPTY_PARAM_MATCH_RESULT;
		}
		MatchResult matchResult = new MatchResult();
		for (int i = 0; i < this.paramNames.length; i++) {
			matchResult.putParameter(paramNames[i], matcher.group(i + 1));
		}
		return matchResult;
	}

	/**
	 * 2个映射是否有冲突
	 * 
	 * @param obj
	 * @return
	 */
	public boolean isReplicated(ActionPathMapping obj) {
		return isPathReplicated(obj) && isMethodsReplicated(obj);
	}

	public boolean isPathReplicated(ActionPathMapping obj) {
		if (this.mapping.equals(obj.mapping)) {
			return true;
		}
		if (this.paramNames.length != obj.paramNames.length) {
			return false;
		}
		if (this.paramsPattern.toString().equals(
				((ActionPathMapping) obj).paramsPattern.toString())) {
			return true;
		}
		return false;
	}

	public boolean isMethodsReplicated(ActionPathMapping obj) {
		String[] methods = new String[] { "GET", "POST", "DELETE", "OPTIONS",
				"PUT", "HEAD" };
		for (String method : methods) {
			if (this.invocation.isRequestMethodSupported(method)
					&& obj.invocation.isRequestMethodSupported(method)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public int compareTo(ActionPathMapping o) {
		boolean thisPlaceHoldered = isPlaceHoldered();
		boolean thatPlaceHoldered = o.isPlaceHoldered();
		if (!thisPlaceHoldered) {
			if (!thatPlaceHoldered) {
				int c = this.mapping.compareTo(o.mapping);
				if (c == 0) {
					boolean e = "*".equals(this.invocation.getRequestMethods());
					boolean n = "*".equals(o.invocation.getRequestMethods());
					if ((e && n)) {
						return 0;
					} else if (!e && !n) {
						return isReplicated(o) ? 0 : this.invocation
								.getRequestMethods().length()
								- o.invocation.getRequestMethods().length();
					} else {
						return e ? 1 : -1;
					}
				}
				return c;
			} else {
				return -1;
			}
		} else {
			if (!thatPlaceHoldered) {
				return 1;
			} else {
				// 变量个数比较多，排在前面
				if (paramNames.length > o.paramNames.length) {
					return -1;
				}
				// 变量个数比较少，排在后面
				else if (paramNames.length < o.paramNames.length) {
					return 1;
				}
				// 相等个数的，看分隔情况
				// ab${id}排在a${id}前面
				else {
					for (int i = 0; i < constaints.size(); i++) {
						if (this.constaints.get(i).equals(o.constaints.get(i))) {
							continue;
						}
						if (this.constaints.get(i).length() == 0) {
							return 1;
						} else if (this.constaints.get(i).startsWith(
								o.constaints.get(i))) {
							return -1;
						} else if (o.constaints.get(i).startsWith(
								this.constaints.get(i))) {
							return 1;
						} else {
							return this.constaints.get(i).compareTo(o.constaints.get(i));
						}
					}
				}
			}
		}
		return 0;
	}

	@Override
	public String toString() {
		return mapping + "(" + paramsPattern + ")" + "->" + invocation;
	}

}
